package lib

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"time"
	"unicode"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/utils"
)

func InitDBPool(path string) error {
	//普通的db方式
	DbConfMap := &MysqlMapConf{}
	err := ParseConfig(path, DbConfMap)
	if err != nil {
		return err
	}
	if len(DbConfMap.List) == 0 {
		fmt.Printf("[INFO] %s%s\n", time.Now().Format(TimeFormat), " empty mysql config.")
	}

	DBMapPool = map[string]*sql.DB{}
	GORMMapPool = map[string]*gorm.DB{}
	for confName, DbConf := range DbConfMap.List {
		dbpool, err := sql.Open("mysql", DbConf.DataSourceName)
		if err != nil {
			return err
		}
		dbpool.SetMaxOpenConns(DbConf.MaxOpenConn)
		dbpool.SetMaxIdleConns(DbConf.MaxIdleConn)
		dbpool.SetConnMaxLifetime(time.Duration(DbConf.MaxConnLifeTime) * time.Second)
		err = dbpool.Ping()
		if err != nil {
			return err
		}

		//gorm连接方式
		dbgorm, err := gorm.Open(mysql.Open(DbConf.DataSourceName), &gorm.Config{})
		if err != nil {
			return err
		}
		db, err := dbgorm.DB()
		if err != nil {
			return err
		}
		err = db.Ping()
		if err != nil {
			return err
		}
		logger := MyGormlogger{Config: logger.Config{LogLevel: logger.Info}}
		dbgorm.Config.Logger = &logger
		db.SetMaxIdleConns(DbConf.MaxIdleConn)
		db.SetMaxOpenConns(DbConf.MaxOpenConn)
		db.SetConnMaxLifetime(time.Duration(DbConf.MaxConnLifeTime) * time.Second)
		DBMapPool[confName] = dbpool
		GORMMapPool[confName] = dbgorm
	}

	//手动配置连接
	if dbpool, err := GetDBPool("default"); err == nil {
		DBDefaultPool = dbpool
	}
	if dbpool, err := GetGormPool("default"); err == nil {
		GORMDefaultPool = dbpool
	}
	return nil
}

func GetDBPool(name string) (*sql.DB, error) {
	if dbpool, ok := DBMapPool[name]; ok {
		return dbpool, nil
	}
	return nil, errors.New("get pool error")
}

func GetGormPool(name string) (*gorm.DB, error) {
	if dbpool, ok := GORMMapPool[name]; ok {
		return dbpool, nil
	}
	return nil, errors.New("get pool error")
}

func CloseDB() error {
	for _, dbpool := range DBMapPool {
		dbpool.Close()
	}
	for _, dbpool := range GORMMapPool {
		db, _ := dbpool.DB()
		db.Close()
	}
	return nil
}

func DBPoolLogQuery(trace *TraceInfo, sqlDb *sql.DB, query string, args ...interface{}) (*sql.Rows, error) {
	startExecTime := time.Now()
	rows, err := sqlDb.Query(query, args...)
	endExecTime := time.Now()
	if err != nil {
		Log.TagError(trace, "_com_mysql_success", map[string]interface{}{
			"sql":       query,
			"bind":      args,
			"proc_time": fmt.Sprintf("%f", endExecTime.Sub(startExecTime).Seconds()),
		})
	} else {
		Log.TagInfo(trace, "_com_mysql_success", map[string]interface{}{
			"sql":       query,
			"bind":      args,
			"proc_time": fmt.Sprintf("%f", endExecTime.Sub(startExecTime).Seconds()),
		})
	}
	return rows, err
}

//mysql日志打印类
type MyGormlogger struct {
	logger.Config
}

const TRACE_CONTEXT = "trace_context"

// LogMode log mode
func (logger *MyGormlogger) LogMode(level logger.LogLevel) logger.Interface {
	newlogger := *logger
	newlogger.LogLevel = level
	return &newlogger
}

// Info print info
func (logger MyGormlogger) Info(ctx context.Context, msg string, data ...interface{}) {
	message := map[string]interface{}{"errMsg": msg, "ext": data}
	trace := GetTraceContext(ctx)
	Log.TagInfo(trace, "_com_mysql_info", message)
}

// Warn print warn messages
func (logger MyGormlogger) Warn(ctx context.Context, msg string, data ...interface{}) {
	message := map[string]interface{}{"errMsg": msg, "ext": data}
	trace := GetTraceContext(ctx)
	Log.TagWarn(trace, "_com_mysql_warn", message)
}

// Error print error messages
func (logger MyGormlogger) Error(ctx context.Context, msg string, data ...interface{}) {
	message := map[string]interface{}{"errMsg": msg, "ext": data}
	trace := GetTraceContext(ctx)
	Log.TagError(trace, "_com_mysql_error", message)
}

// Trace print sql message
func (logger MyGormlogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
	elapsed := time.Since(begin)
	sql, rows := fc()
	source := utils.FileWithLineNum()
	messages := map[string]interface{}{"errMsg": err, "sql": sql, "source": source, "current_time": logger.NowFunc(),
		"elapsed": float64(elapsed.Nanoseconds()) / 1e6, "affected_row": rows}
	trace := GetTraceContext(ctx)
	switch {
	case err != nil && !errors.Is(err, gorm.ErrRecordNotFound):
		Log.TagError(trace, "_com_mysql_error", messages)
	case elapsed > logger.SlowThreshold && logger.SlowThreshold != 0:
		Log.TagWarn(trace, "_com_mysql_warn", messages)
	default:
		Log.TagDebug(trace, "_com_mysql_info", messages)
	}
}

func (logger *MyGormlogger) NowFunc() time.Time {
	return time.Now()
}

func (logger *MyGormlogger) isPrintable(s string) bool {
	for _, r := range s {
		if !unicode.IsPrint(r) {
			return false
		}
	}
	return true
}
